package com.chengqs.summer.io;

import jakarta.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.*;
import java.util.*;
import java.util.function.Function;

/**
 * {@code PropertyResolver} 类是一个用于解析和管理属性的工具类，
 * 它可以从系统环境变量和 Java {@link Properties} 对象中读取属性。
 *
 * <p>该类的设计中应用了多种设计模式，以增强其灵活性和可维护性：
 *
 * <ul>
 *   <li><strong>策略模式 (Strategy Pattern)(行为):</strong>
 *   <p>策略模式用于将字符串值转换为各种 Java 类型。
 *   {@code converters} 映射表充当策略的注册器，允许在运行时动态选择合适的转换策略。
 *
 *   <pre>{@code
 *   converters.put(String.class, s -> s);
 *   converters.put(boolean.class, s -> Boolean.parseBoolean(s));
 *   converters.put(Integer.class, s -> Integer.valueOf(s));
 *   }</pre>
 *
 *   <p>每种类型转换由一个特定的 {@link Function} 表示，从而实现了不同转换算法的使用，
 *   而无需修改类的结构。
 *
 *   <li><strong>工厂模式 (Factory Pattern)(创建):</strong>
 *   <p>工厂模式在创建 {@link PropertyExpr} 对象时得到了应用，
 *   通过 {@code parsePropertyExpr} 方法封装了表达式对象的创建逻辑。
 *   该方法根据特定的语法规则（例如 {@code ${key:defaultValue} }）动态生成表达式对象。
 *
 *   <pre>{@code
 *   PropertyExpr parsePropertyExpr(String key) {
 *       if (key.startsWith("${") && key.endsWith("}")) {
 *           int n = key.indexOf(':');
 *           if (n == (-1)) {
 *               String k = notEmpty(key.substring(2, key.length() - 1));
 *               return new PropertyExpr(k, null);
 *           } else {
 *               String k = notEmpty(key.substring(2, n));
 *               return new PropertyExpr(k, key.substring(n + 1, key.length() - 1));
 *           }
 *       }
 *       return null;
 *   }
 *   }</pre>
 *
 *   <p>通过这种方式，创建过程得到了封装，调用者无需直接处理对象的生成细节。
 *
 *   <li><strong>模板方法模式 (Template Method Pattern)(行为):</strong>
 *   <p>模板方法模式在属性获取过程中得到了体现。{@code getProperty} 方法定义了获取属性的通用步骤，
 *   并通过调用不同的方法实现了解析和转换的具体细节。
 *
 *   <pre>{@code
 *   String getProperty(String key) {
 *       PropertyExpr keyExpr = parsePropertyExpr(key);
 *       if (keyExpr != null) {
 *           if (keyExpr.defaultValue() != null) {
 *               return getProperty(keyExpr.key(), keyExpr.defaultValue());
 *           } else {
 *               return getRequiredProperty(keyExpr.key());
 *           }
 *       }
 *       String value = this.properties.get(key);
 *       if (value != null) {
 *           return parseValue(value);
 *       }
 *       return value;
 *   }
 *   }</pre>
 *
 *   <p>此模式允许在保持算法骨架不变的情况下，灵活定义解析和转换的实现细节。
 *
 *   <li><strong>解释器模式 (Interpreter Pattern)(行为):</strong>
 *   <p>解释器模式用于解析属性表达式中的特殊语法（如 `${}`），并动态替换属性值。
 *   {@code parsePropertyExpr} 方法实现了解释器的逻辑，通过解析表达式语法，生成相应的解析结果。
 *
 *   <pre>{@code
 *   PropertyExpr parsePropertyExpr(String key) {
 *       if (key.startsWith("${") && key.endsWith("}")) {
 *           int n = key.indexOf(':');
 *           if (n == (-1)) {
 *               String k = notEmpty(key.substring(2, key.length() - 1));
 *               return new PropertyExpr(k, null);
 *           } else {
 *               String k = notEmpty(key.substring(2, n));
 *               return new PropertyExpr(k, key.substring(n + 1, key.length() - 1));
 *           }
 *       }
 *       return null;
 *   }
 *   }</pre>
 *
 *   <p>这种设计使得属性解析变得灵活且可扩展。
 *
 *   <li><strong>责任链模式 (Chain of Responsibility Pattern)(行为):</strong>
 *   <p>责任链模式通过一系列方法调用形成了处理链，用于属性值的解析和默认值的提供。
 *   {@code getProperty} 和 {@code parseValue} 方法之间的链式调用即实现了这种模式。
 *
 *   <pre>{@code
 *   public String getProperty(String key, String defaultValue) {
 *       String value = getProperty(key);
 *       return value == null ? parseValue(defaultValue) : value;
 *   }
 *   }</pre>
 *
 *   <p>每个方法在责任链中承担不同的职责，处理请求的一部分直到请求被完全处理。
 *
 *   <li><strong>装饰者模式 (Decorator Pattern)(结构):</strong>
 *   <p>装饰者模式允许动态地向对象添加功能。尽管 `PropertyResolver` 并没有直接使用装饰者模式，
 *   但通过 {@code parseValue} 方法的解析和替换逻辑，实现了类似装饰者模式的功能。
 *
 *   <pre>{@code
 *   String parseValue(String value) {
 *       PropertyExpr expr = parsePropertyExpr(value);
 *       if (expr == null) {
 *           return value;
 *       }
 *       if (expr.defaultValue() != null) {
 *           return getProperty(expr.key(), expr.defaultValue());
 *       } else {
 *           return getRequiredProperty(expr.key());
 *       }
 *   }
 *   }</pre>
 *
 *   <p>该方法通过解析表达式并应用替换逻辑，对属性值进行了动态的增强。
 *
 *   <li><strong>适配器模式 (Adapter Pattern)(结构):</strong>
 *   <p>适配器模式用于将一种接口转换为另一种接口，使得不兼容的接口能够协同工作。
 *   在 {@code PropertyResolver} 中，{@code converters} 映射表通过将字符串转换为目标类型的接口，
 *   实现了适配器模式。
 *
 *   <pre>{@code
 *   <T> T convert(Class<?> clazz, String value) {
 *       Function<String, Object> fn = this.converters.get(clazz);
 *       if (fn == null) {
 *           throw new IllegalArgumentException("Unsupported value type: " + clazz.getName());
 *       }
 *       return (T) fn.apply(value);
 *   }
 *   }</pre>
 *
 *   <p>这种设计使得转换器能够以统一的方式工作，增强了代码的灵活性。
 * </ul>
 *
 * <p>通过应用这些设计模式，{@code PropertyResolver} 类实现了高度的灵活性、可扩展性和可维护性，
 * 能够有效地解析和管理复杂的属性配置。
 *
 * @see Properties
 * @see Function
 */
public class PropertyResolver {

    Logger logger = LoggerFactory.getLogger(getClass());

    // // 用于存储属性键值对的 Map，包括从系统环境变量和 Properties 对象中读取的属性。
    Map<String, String> properties = new HashMap<>();
    // 用于存储类型转换器的 Map，将字符串属性值转换为不同的 Java 类型（如 Integer、LocalDate 等）。
    Map<Class<?>, Function<String, Object>> converters = new HashMap<>(); // 存储Class -> Function:

    /**
     * 构造函数，初始化属性键值对 Map 和类型转换器 Map。
     * 将系统环境变量和传入的 Properties 对象中的属性合并到内部的 properties map 中。
     * 注册各种类型的转换器。
     *
     * @param props Properties 对象，用于读取属性键值对。
     */
    public PropertyResolver(Properties props) {
        // 将系统环境变量中的所有键值对加入到 properties map
        this.properties.putAll(System.getenv());
        // 获取 Properties 对象中所有属性的键
        Set<String> names = props.stringPropertyNames();
        // 将这些属性值添加到 properties map 中
        for (String name : names) {
            this.properties.put(name, props.getProperty(name));
        }
        if (logger.isDebugEnabled()) {
            List<String> keys = new ArrayList<>(this.properties.keySet());
            Collections.sort(keys);
            for (String key : keys) {
                logger.debug("PropertyResolver: {} = {}", key, this.properties.get(key));
            }
        }
        // register converters:
        // 注册转换器，将字符串转换为不同类型的值
        // String类型
        converters.put(String.class, s -> s);
        // boolean类型
        converters.put(boolean.class, Boolean::parseBoolean);
        converters.put(Boolean.class, Boolean::valueOf);
        converters.put(byte.class, Byte::parseByte);
        converters.put(Byte.class, Byte::valueOf);
        converters.put(short.class, Short::parseShort);
        converters.put(Short.class, Short::valueOf);
        converters.put(int.class, Integer::parseInt);
        converters.put(Integer.class, Integer::valueOf);
        converters.put(long.class, Long::parseLong);
        converters.put(Long.class, Long::valueOf);
        converters.put(float.class, Float::parseFloat);
        converters.put(Float.class, Float::valueOf);
        converters.put(double.class, Double::parseDouble);
        converters.put(Double.class, Double::valueOf);
        // Date/Time类型:
        converters.put(LocalDate.class, LocalDate::parse);
        converters.put(LocalTime.class, LocalTime::parse);
        converters.put(LocalDateTime.class, LocalDateTime::parse);
        converters.put(ZonedDateTime.class, ZonedDateTime::parse);
        converters.put(Duration.class, Duration::parse);
        converters.put(ZoneId.class, ZoneId::of);
    }

    /**
     * 检查指定的属性键是否存在。
     * @param key 要检查的属性键。
     * @return 如果 properties map 中包含指定的键，则返回 true；否则返回 false。
     */
    public boolean containsProperty(String key) {
        return this.properties.containsKey(key);
    }

    /**
     * 获取指定键的属性值，支持 ${key:defaultValue} 格式的属性值替换
     * @param key 要获取的属性键。
     * @return 如果找到对应的属性键，则返回属性值；如果未找到，则返回 null。如果属性值中包含 ${} 表达式，则解析并返回解析后的值。
     */
    @Nullable
    public String getProperty(String key) {
        // 解析键是否为 ${key:defaultValue} 格式
        PropertyExpr keyExpr = parsePropertyExpr(key);
        if (keyExpr != null) {
            // 如果是，则调用 getProperty 或 getRequiredProperty 方法来获取属性值。
            if (keyExpr.defaultValue() != null) {
                // 带默认值查询:
                return getProperty(keyExpr.key(), keyExpr.defaultValue());
            } else {
                // 不带默认值查询:
                return getRequiredProperty(keyExpr.key());
            }
        }
        // 普通key查询:
        String value = this.properties.get(key);
        if (value != null) {
            // 解析值中可能包含的 ${} 表达式。
            return parseValue(value);
        }
        return value;
    }

    /**
     * 获取指定键的属性值，如果未找到，则返回默认值。
     * @param key 要获取的属性键。
     * @param defaultValue 如果未找到属性键，则返回的默认值。
     * @return 如果找到属性键，则返回属性值；如果未找到，则返回解析后的默认值。
     */
    public String getProperty(String key, String defaultValue) {
        // 调用 getProperty 方法获取属性值。
        String value = getProperty(key);
        // 如果值为空，返回解析后的默认值，否则返回找到的值。
        return value == null ? parseValue(defaultValue) : value;
    }

    /**
     * 获取指定键的属性值，并将其转换为目标类型。
     * @param key 要获取的属性键。
     * @param targetType 目标类型的 Class 对象，用于将属性值转换为该类型。
     * @return 转换后的目标类型值。如果未找到属性值，则返回 null。
     * @param <T> 目标类型的泛型参数。
     */
    @Nullable
    public <T> T getProperty(String key, Class<T> targetType) {
        // 获取属性值。
        String value = getProperty(key);
        if (value == null) {
            return null;
        }
        // 转换为指定类型:
        return convert(targetType, value);
    }

    /**
     * 获取指定键的属性值，并将其转换为目标类型。如果未找到属性值，则返回默认值。
     * @param key 要获取的属性键。
     * @param targetType 目标类型的 Class 对象，用于将属性值转换为该类型。
     * @param defaultValue 如果未找到属性键，则返回的默认值。
     * @return 转换后的目标类型值。如果未找到属性值，则返回指定的默认值。
     * @param <T> 目标类型的泛型参数。
     */
    public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
        // // 获取属性值。
        String value = getProperty(key);
        if (value == null) {
            // 如果值为空，返回默认值。
            return defaultValue;
        }
        // 否则，将值转换为目标类型并返回。
        return convert(targetType, value);
    }

    /**
     * 获取指定键的属性值，如果未找到则抛出异常。
     * @param key 要获取的属性键。
     * @return 获取到的属性值。如果未找到属性值，则抛出异常。
     */
    public String getRequiredProperty(String key) {
        // 获取属性值。
        String value = getProperty(key);
        // 如果值为空，抛出 NullPointerException，否则返回值。
        return Objects.requireNonNull(value, "Property '" + key + "' not found.");
    }

    /**
     * 获取指定键的属性值，并将其转换为目标类型。如果未找到则抛出异常。
     * @param key 要获取的属性键。
     * @param targetType 目标类型的 Class 对象，用于将属性值转换为该类型。
     * @return 转换后的目标类型值。如果未找到属性值，则抛出异常。
     * @param <T> 目标类型的泛型参数。
     */
    public <T> T getRequiredProperty(String key, Class<T> targetType) {
        // 获取并转换属性值。
        T value = getProperty(key, targetType);
        // 如果值为空，抛出 NullPointerException，否则返回值。
        return Objects.requireNonNull(value, "Property '" + key + "' not found.");
    }

    /**
     * 将字符串值转换为指定的目标类型。
     * @param clazz 目标类型的 Class 对象。
     * @param value 要转换的字符串值。
     * @return 转换后的目标类型值。
     * @param <T> 目标类型的泛型参数。
     */
    @SuppressWarnings("unchecked")
    <T> T convert(Class<?> clazz, String value) {
        // 获取目标类型的转换器函数。
        Function<String, Object> fn = this.converters.get(clazz);
        if (fn == null) {
            // 如果没有找到转换器，抛出 IllegalArgumentException。
            throw new IllegalArgumentException("Unsupported value type: " + clazz.getName());
        }
        // 将字符串值应用到转换器函数中，转换为目标类型。
        return (T) fn.apply(value);
    }

    /**
     * 解析值中的 ${} 表达式，进行属性值替换。
     * @param value 要解析的属性值。
     * @return 解析后的属性值。如果值中包含 ${} 表达式，则返回替换后的值。
     */
    String parseValue(String value) {
        // 解析值中的表达式。
        PropertyExpr expr = parsePropertyExpr(value);
        if (expr == null) {
            return value;
        }
        // 根据是否有默认值，获取解析后的值。
        if (expr.defaultValue() != null) {
            return getProperty(expr.key(), expr.defaultValue());
        } else {
            return getRequiredProperty(expr.key());
        }
    }

    /**
     * 解析 ${key:defaultValue} 格式的属性表达式，提取键和值。
     * @param key 要解析的属性表达式。
     * @return 包含键和默认值的记录对象。如果表达式格式不正确，则返回 null。
     */
    PropertyExpr parsePropertyExpr(String key) {
        // 检查是否是 ${key:defaultValue} 格式。
        if (key.startsWith("${") && key.endsWith("}")) {
            // 查找 : 位置来判断是否有默认值。
            int n = key.indexOf(':');
            if (n == (-1)) {
                // 没有defaultValue: ${key}
                String k = notEmpty(key.substring(2, key.length() - 1));
                return new PropertyExpr(k, null);
            } else {
                // 有defaultValue: ${key:default}
                String k = notEmpty(key.substring(2, n));
                return new PropertyExpr(k, key.substring(n + 1, key.length() - 1));
            }
        }
        return null;
    }

    /**
     * 检查键是否为空。如果为空则抛出异常。
     * @param key 要检查的键。
     * @return 如果键非空，则返回键；否则抛出异常。
     */
    String notEmpty(String key) {
        if (key.isEmpty()) {
            // 检查键是否为空，并抛出 IllegalArgumentException
            throw new IllegalArgumentException("Invalid key: " + key);
        }
        return key;
    }
}

record PropertyExpr(String key, String defaultValue) {
}
